﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace AMatysiak.Comparers
{
    public class AlphaNumComparer : IComparer
    {
        private sealed class CompareValue
        {
            private enum ChunkType { Alphanumeric, Numeric };

            private string value;

            private int marker;

            private char character;

            private StringBuilder chunk;

            private ChunkType type;

            public CompareValue(object valueObject)
            {
                value = valueObject.ToString();
                marker = 0;
            }

            public bool IsInvalid()
            {
                return value == null;
            }

            public bool AreCharsToCompare()
            {
                return marker < value.Length;
            }

            public bool IsReachEnd()
            {
                return marker >= value.Length;
            }

            public void NextCharacterIsBeginOfChunk()
            {
                character = value[marker];
                chunk = new StringBuilder();

                setTypeOfChunk();
            }

            private void setTypeOfChunk()
            {
                type = ChunkType.Alphanumeric;

                if (isFirstCharANumber())
                    type = ChunkType.Numeric;
            }

            private bool isFirstCharANumber()
            {
                return char.IsDigit(character);
            }

            public bool AreCharsOfEqualType()
            {
                return AreCharsToCompare() && hasChunkEqualType();
            }

            private bool hasChunkEqualType()
            {
                return chunk.Length == 0 || isChunkContainsCharsOfEqualType();
            }

            private bool isChunkContainsCharsOfEqualType()
            {
                if (hasCurrentCharInValidType())
                    return false;

                return true;
            }

            private bool hasCurrentCharInValidType()
            {
                return isCurrentCharAnInvalidAlphanumeric() || isCurrentCharAnInvalidNumeric();
            }

            private bool isCurrentCharAnInvalidAlphanumeric()
            {
                return type == ChunkType.Alphanumeric && char.IsDigit(character);
            }

            private bool isCurrentCharAnInvalidNumeric()
            {
                return type == ChunkType.Numeric && !char.IsDigit(character);
            }

            public void AppendNextCharToChunk()
            {
                appendCurrentCharacterToChunk();

                actualizeCurrentCharacter();
            }

            private void appendCurrentCharacterToChunk()
            {
                chunk.Append(character);
                marker++;
            }

            private void actualizeCurrentCharacter()
            {
                if (marker < value.Length)
                    character = value[marker];
            }

            public bool IsFirstCharNumeric()
            {
                return char.IsDigit(chunk[0]);
            }

            public int GetChunkAsInt()
            {
                return int.Parse(chunk.ToString());
            }

            public string GetChunk()
            {
                return chunk.ToString();
            }
        }

        private CompareValue firstValue;
        private CompareValue secondValue;

        private enum ComparationValue
        {
            SMALLER = -1,
            EQUAL = 0,
            BIGGER = 1,
            NO_RESULT
        }

        private ComparationValue resultValue = ComparationValue.EQUAL;

        public int Compare(object x, object y)
        {
            if (equalValues(x, y))
                return (int)ComparationValue.EQUAL;

            initizalizeObjects(x, y);

            validateValues();

            compareCharsOfValues();

            return returnResultValue();
        }

        private bool equalValues(object x, object y)
        {
            return x == y;
        }

        private void initizalizeObjects(object x, object y)
        {
            firstValue = new CompareValue(x);
            secondValue = new CompareValue(y);
            resultValue = ComparationValue.EQUAL;
        }

        private void validateValues()
        {
            if (isOneOfValueNull())
                resultValue = ComparationValue.NO_RESULT;
            else
                resultValue = ComparationValue.EQUAL;
        }

        private bool isOneOfValueNull()
        {
            return firstValue.IsInvalid() || secondValue.IsInvalid();
        }

        private void compareCharsOfValues()
        {
            while (areCharsToCompareOnBothSidesAndCanProceed())
            {
                checkIsAnyValueReachEnd();

                compareByType();
            }
        }

        private bool areCharsToCompareOnBothSidesAndCanProceed()
        {
            return !haveAResult() && (firstValue.AreCharsToCompare() || secondValue.AreCharsToCompare());
        }

        private bool haveAResult()
        {
            return resultValue != ComparationValue.EQUAL;
        }

        private void checkIsAnyValueReachEnd()
        {
            if (firstValue.IsReachEnd())
                resultValue = ComparationValue.SMALLER;
            else if (secondValue.IsReachEnd())
                resultValue = ComparationValue.BIGGER;
        }

        private void compareByType()
        {
            if (haveAResult())
                return;

            analysisRestOfValues();

            if (areFirstCharsNumerics())
                compareNumerically();
            else
                compareAlphabetically();
        }

        private void analysisRestOfValues()
        {
            firstValue.NextCharacterIsBeginOfChunk();
            secondValue.NextCharacterIsBeginOfChunk();

            while (firstValue.AreCharsOfEqualType())
                firstValue.AppendNextCharToChunk();

            while (secondValue.AreCharsOfEqualType())
                secondValue.AppendNextCharToChunk();
        }

        private bool areFirstCharsNumerics()
        {
            return firstValue.IsFirstCharNumeric() && secondValue.IsFirstCharNumeric();
        }

        private void compareNumerically()
        {
            int firstNumeric = firstValue.GetChunkAsInt();
            int secondNumeric = secondValue.GetChunkAsInt();

            compareTwoDigits(firstNumeric, secondNumeric);
        }

        private void compareTwoDigits(int firstNumeric, int secondNumeric)
        {
            if (firstNumeric < secondNumeric)
                resultValue = ComparationValue.SMALLER;
            else if (firstNumeric > secondNumeric)
                resultValue = ComparationValue.BIGGER;
        }

        private void compareAlphabetically()
        {
            string firstChunk = firstValue.GetChunk();
            string secondChunk = secondValue.GetChunk();

            compareTwoChars(firstChunk, secondChunk);
        }

        private void compareTwoChars(string firstChunk, string secondChunk)
        {
            resultValue = (ComparationValue)firstChunk.CompareTo(secondChunk);
        }

        private int returnResultValue()
        {
            if (resultValue == ComparationValue.NO_RESULT)
                resultValue = ComparationValue.EQUAL;

            return (int)resultValue;
        }
    }
}
